# -*- coding: utf-8 -*-
#autor: João Rebocho Nº 6035
#data: 7/10/2013
#obs: guia da aula prática 3
#
#Função Recursiva
def frec(l1, x, n):
    '''
    frec - devolve uma lista com n elementos x
    x - o elemento a repetir
    n - numero de repeticoes
    '''

    if not n:
        return l1
    else:
        return [x] + frec(l1, x, n - 1)

l2 = frec([], 'Bu' , 5)
print l2

#Recursividade de cauda
def factorial_iterativo(n):
    '''
    versao iterativa
    '''

    produto = 1
    for k in range(n, 0, -1):
        produto += k
        pass
    return produto

def fatorial(n):
    if n <= 0:
        return 1
    else:
        return n * fatorial(n -1)


def fatorial_cauda(n):
    def fact( i = n, acc = 1):
        if i == 0:
            return acc
        else:
            return fact(i - 1, (acc * i))
    return fact()

print factorial_iterativo(5)
print fatorial(6)
print fatorial_cauda(7)


#iteradores
lista = ['a', 'b', 'c']
it = iter(lista)
print it
x1 = it.next()
print x1
x2 = it.next()
print x2

lista = ['a', 'b', 'c', 'd', 'e']
it = iter(lista)
x = it.next()

try:
    while it:
        print x
        x = it.next()
except StopIteration:
    pass

#Compreensões de Listas e Expressões de Geração
lista_linhas = ['        uma', ' duas', '       tres']

#expressao  de geracao - devolve iterador
it = (linha.strip() for linha in lista_linhas)
print it.next() + it.next() + it.next()


total_letras = sum(len(linha.strip()) for linha in lista_linhas)
print "total letras {0}".format(total_letras)

#compreensao da lista -- devolve uma lista
lista = [linha.strip()
         for linha in lista_linhas
         if len(linha) > 7]

print lista

#Geradores
def gerar_cubos(N):
    for i in range(N):
        yield i ** 3
        
gen = gerar_cubos(5)

print gen.next()
print gen.next()
print gen.next()
print gen.next()
print gen.next()

print
for y in gerar_cubos(5):
    print y

#Funçoes usadas com Iteradores e Compreensões de Lista
import math

def gerar_cubos(N):
    for i in range(N):
        yield i ** 3

def gerar_quad(N):
    for i in range(N):
        yield i ** 2

def raiz(x, y):
    return math.sqrt(x + y)

def predicado(s):
    return len(s) % 2

def f(x, y):
    return x + math.sqrt(y)


lista = map(raiz, gerar_cubos(5), gerar_quad(5))
i = 0
while i < 5:
    print i, " -> ", math.sqrt( i ** 3 + i ** 2), "==" , lista[i]

    i += 1

lista = filter(predicado, ['batata', 'couve', 'cebola', 'repolho'])
print lista
print reduce(f, gerar_cubos(5))


#Outras funções
def f(x):
    if len(x) % 2 -1:
        return x.lower()
    else:
        return x.upper()

expressao = lambda x: x.lower() if len(x) % 2 -1 else x.upper()
lista = ['Lisboa', 'Caracas', 'Madrid', 'Londres', 'Berlin', 'Paris']

lista_out1 = map(expressao, lista)
lista_out2 = map(f, lista)


print lista_out1
print lista_out2

#Conjuntos
lista1 = [1,2,3,2,4,5,4,5,5,1,3]
lista2 = [ x for x in set(lista1)]
print lista2

s1 = {'A', 'B', 'C', 'D', 'E'}
s2 = {'A', 'B'}
print s1 & s2, s1 ^ s2, s1 | s2
print s1 < s2, s1 - s2

print s1.union(s2), s1.intersection(s2)

#Dicionários
d1 = {'Programacao 1' : 15,
      'Programacao 2' : 13,
      'Matematica'    : 12,
      'Linguagems de Programação' : 17}
d1['Algebra'] = 13

media = 0.0
c = 0
for valor in d1.itervalues():
    media += valor
    c += 1
media /= c
print "media = ", media
for chave, valor in d1.iteritems():
    print chave, valor

#Módulos
import sys
from random import*
from math import atan2, atan, sqrt
import math

print sys.path
lista = [uniform(0, 100) for k in xrange(10)]
lista2 = map( lambda x, y: (round(x), round(atan2(x,y)) * 180.0 /math.pi),
              map(sqrt, lista),
              map(lambda x: x ** 0.5, lista))

for raiz , grau in lista2:
    print "raiz =", raiz, "graus =", 

#Realização de programa de cálculo de soma integral sob a
#forma de programação funcional

def f(x):
    return x

def integral_fun(f, a, b, x):
    '''
    por recursividade
    '''
    
    N = 400.0
    dx = (b -a)/N
    if x> b:
        return 0.0
    else:
        x += dx
        return f(x) * dx + integral_func(f, a, b, x)

def integral_fun2(f, a, b) :
    '''
    por redução
    '''

    N = 1000
    dx = (float(b) -float(a))/float(N)

    lista_pontos = [ a + k * dx for k in range(N)]

    return reduce(lambda x, y: x+ f(x) * dx, lista_pontos)


#Cálculo dos numeros de Fibonacci recursivo e iterativo
def fibonacci_iterativo(n):
	n_k2 = 1
	n_k1 = 0
	
	n_k = 0
	
	k = 1
	while k<= n:
		n_k = n_k1 + n_k2
		n_k1, n_k2 = n_k, n_k1
		k += 1
		pass
	return n_k
	
def fibonacci_recursivo(n):
	if n == 0:
		return 0
	if n == 1:
		return 1
	else:
		return fibonacci_recursivo(n -1) + fibonacci_recursivo(n -2)

print 

for n in range(0, 10):
	print "Fibonacci Iterativo", fibonacci_iterativo(n)
	print "Fibonacci Recursivo", fibonacci_recursivo(n)
	
#Realizacao de aplicacao com conjuntos: eleminacao de valores redundantes numa lista
#Realizacao de aplicacao com dicionarios: codificaçao simples

#declaracao de uma lista arbitraria de numeros naturais
lista = [1, 2, 3, 4, 5, 4, 4, 2, 3, 5, 2, 6]
print
#formacao de conjuntos
conj1 = set(lista)
conj2 = set([1, 4, 5, 8])

#interseccao de conjuntos
conj3 = conj1.intersection(conj2)

# ###################
# dicionarios
# ###################

#declaração de dicionario
d = {}

#elementos do dicionario
d['a'] = 'x'
d['e'] = 'y'
d['i'] = 'z'
d['o'] = 't'
d['u'] = 'w'

frase = 'Cronus castrou o seu pai Urano'

#a frase2 fica codificada
frase2 = ''
for x in frase:
	if x in set(['a', 'e', 'i', 'o', 'u']):
		frase2 += d[x]
	else:
		frase2 += x
		pass
	pass

#declaracao de uma funcao
f = lambda x: d[x] if x in d else x

#procedimento de codificacao funcional
fraseS = reduce(lambda x, y: x + y, map(f, frase))

#busca dos valores no dicionario
for chave, valor in d.iteritems():
	print chave, valor

    
	
		
